import { InjectRedis } from '@liaoliaots/nestjs-redis';
import { Inject, Injectable, Logger } from '@nestjs/common';
import Redis from 'ioredis';
import { isEmpty } from 'lodash';
import { BusinessException } from '~/common/exceptions/biz.exception';
import { AppConfig, IAppConfig, ISecurityConfig, SecurityConfig } from '~/config';
import { ErrorEnum } from '~/constants/error-code.constant';
import {
  genAuthPVKey,
  genAuthPermKey,
  genAuthTokenKey,
  genTokenBlacklistKey,
} from '~/helper/genRedisKey';
import { UserService } from '~/modules/user/user.service';
import { md5 } from '~/utils';
import { LoginLogService } from '../system/log/services/login-log.service';
import { MenuService } from '../system/menu/menu.service';
import { RoleService } from '../system/role/role.service';
import { TokenService } from './services/token.service';
import { UserEntity } from '~/modules/user/user.entity';
import { LoginDto } from './dto/auth.dto';
import { UserAdminService } from '../user/user-admin/user-admin.service';
import { UserAdminEntity } from '../user/user-admin/user-admin.entity';

@Injectable()
export class AuthService {
  private readonly logger = new Logger(AuthService.name);

  constructor(
    @InjectRedis() private readonly redis: Redis,
    private menuService: MenuService,
    private roleService: RoleService,
    private userService: UserService,
    private userAdminService: UserAdminService,
    private loginLogService: LoginLogService,
    private tokenService: TokenService,
    @Inject(SecurityConfig.KEY) private securityConfig: ISecurityConfig,
    @Inject(AppConfig.KEY) private appConfig: IAppConfig
  ) {}

  async validateUser(credential: string, password: string): Promise<any> {
    const user: UserAdminEntity = await this.userAdminService.getByUsername(credential);

    if (isEmpty(user)) throw new BusinessException(ErrorEnum.USER_NOT_FOUND);

    const comparePassword = md5(`${password}${user.psalt}`);
    if (user.password !== comparePassword)
      throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD);

    if (user) {
      const { password, ...result } = user;
      return result;
    }

    return null;
  }

  /**
   * 获取登录JWT
   * 返回null则账号密码有误，不存在该用户
   */
  async login(dto: LoginDto): Promise<string> {
    const adminUser: UserAdminEntity = await this.userAdminService.getByUsername(dto.username);

    if (isEmpty(adminUser)) {
      throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD);
    }

    const comparePassword = md5(`${dto.password}${adminUser.psalt}`);
    const defaultPassword = md5(`${adminUser.password}${adminUser.psalt}`);

    if (defaultPassword !== comparePassword) {
      throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD);
    }

    const user: UserEntity = await this.userService.getUserByAdminId(adminUser.id);

    const roleId = await this.roleService.getRoleIdsByUser(user.id);

    const roles = await this.roleService.getRoleValues([roleId]);

    const token = await this.tokenService.generateAccessToken(user.id, roles);

    await this.redis.set(
      genAuthTokenKey(user.id),
      token.accessToken,
      'EX',
      this.securityConfig.jwtExprire
    );

    // 设置密码版本号 当密码修改时，版本号+1
    await this.redis.set(genAuthPVKey(user.id), 1);

    // 设置菜单权限
    const permissions = await this.menuService.getPermissions(user.id);
    await this.setPermissionsCache(user.id, permissions);

    return token.accessToken;
  }

  /**
   * 效验账号密码
   */
  async checkPassword(username: string, password: string) {
    const user = await this.userService.findUserByUserName(username);

    // const comparePassword = md5(`${password}${user.psalt}`);
    // if (user.password !== comparePassword)
    //   throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD);
  }

  async loginLog(uid: number, ip: string, ua: string) {
    await this.loginLogService.create(uid, ip, ua);
  }

  /**
   * 重置密码
   */
  async resetPassword(username: string, password: string) {
    const user = await this.userService.findUserByUserName(username);

    await this.userService.forceUpdatePassword(user.id, password);
  }

  /**
   * 清除登录状态信息
   */
  async clearLoginStatus(user: IAuthUser, accessToken: string): Promise<void> {
    const exp = user.exp
      ? (user.exp - Date.now() / 1000).toFixed(0)
      : this.securityConfig.jwtExprire;
    await this.redis.set(genTokenBlacklistKey(accessToken), accessToken, 'EX', exp);
    if (this.appConfig.multiDeviceLogin) await this.tokenService.removeAccessToken(accessToken);
    else await this.userService.forbidden(user.uid, accessToken);
  }

  /**
   * 获取菜单列表
   */
  async getMenus(uid: number) {
    return this.menuService.getMenus(uid);
  }

  /**
   * 获取权限列表
   */
  async getPermissions(uid: number): Promise<string[]> {
    return this.menuService.getPermissions(uid);
  }

  async getPermissionsCache(uid: number): Promise<string[]> {
    const permissionString = await this.redis.get(genAuthPermKey(uid));
    return permissionString ? JSON.parse(permissionString) : [];
  }

  async setPermissionsCache(uid: number, permissions: string[]): Promise<void> {
    await this.redis.set(genAuthPermKey(uid), JSON.stringify(permissions));
  }

  async getPasswordVersionByUid(uid: number): Promise<string> {
    return this.redis.get(genAuthPVKey(uid));
  }

  async getTokenByUid(uid: number): Promise<string> {
    return this.redis.get(genAuthTokenKey(uid));
  }

  async checkAccessToken(accessToken: string): Promise<boolean> {
    return this.tokenService.checkAccessToken(accessToken);
  }

	async getDevTempToken(uid: number): Promise<string> {
		const token = await this.tokenService.generateAccessToken(uid, []);

    await this.redis.set(
      genAuthTokenKey(uid),
      token.accessToken,
      'EX',
      this.securityConfig.jwtExprire
    );

		return token.accessToken;
	}
}
